#include "types.h"
#include "globals.h"
#include "kernel.h"
#include "errno.h"

#include "util/debug.h"

#include "proc/proc.h"

#include "mm/mm.h"
#include "mm/mman.h"
#include "mm/page.h"
#include "mm/mmobj.h"
#include "mm/pframe.h"
#include "mm/pagetable.h"

#include "vm/pagefault.h"
#include "vm/vmmap.h"

/*
 * This gets called by _pt_fault_handler in mm/pagetable.c The
 * calling function has already done a lot of error checking for
 * us. In particular it has checked that we are not page faulting
 * while in kernel mode. Make sure you understand why an
 * unexpected page fault in kernel mode is bad in Weenix. You
 * should probably read the _pt_fault_handler function to get a
 * sense of what it is doing.
 *
 * Before you can do anything you need to find the vmarea that
 * contains the address that was faulted on. Make sure to check
 * the permissions on the area to see if the process has
 * permission to do [cause]. If either of these checks does not
 * pass kill the offending process, setting its exit status to
 * EFAULT (normally we would send the SIGSEGV signal, however
 * Weenix does not support signals).
 *
 * Now it is time to find the correct page (don't forget
 * about shadow objects, especially copy-on-write magic!). Make
 * sure that if the user writes to the page it will be handled
 * correctly.
 *
 * Finally call pt_map to have the new mapping placed into the
 * appropriate page table.
 *
 * @param vaddr the address that was accessed to cause the fault
 *
 * @param cause this is the type of operation on the memory
 *              address which caused the fault, possible values
 *              can be found in pagefault.h
 */
void
handle_pagefault(uintptr_t vaddr, uint32_t cause)
{
	dbgq(DBG_THR, "handle_pagefault(): enter, map vaddr is %08x, cause is %d\n", vaddr, cause);
	int ret = 0;
	int forwrite = 0;
	uint32_t vfn = ADDR_TO_PN(vaddr);
	uint32_t paddr = 0;
	uint32_t pagenum = 0;
	uint32_t pdflags = 0;
	uint32_t ptflags = 0;
	vmarea_t *the_vmarea = NULL;
	mmobj_t *the_mmobj = NULL;
	pframe_t *the_pframe = NULL;

	the_vmarea = vmmap_lookup(curproc->p_vmmap, vfn);
	if (the_vmarea == NULL) {
		proc_kill(curproc, EFAULT);	
	}

	if (cause & FAULT_WRITE) {
		if (!(the_vmarea->vma_prot & PROT_WRITE)) {
			proc_kill(curproc, EFAULT);	
		}
	}
	if (cause & FAULT_EXEC) {
		if (!(the_vmarea->vma_prot & PROT_EXEC)) {
			proc_kill(curproc, EFAULT);	
		}
	}

	/* now begin to find the corrent page */
	dbgq(DBG_THR, "handle_pagefault(): begin to find correct page\n");
	if (cause & FAULT_WRITE) {
		pdflags = PD_WRITE | PD_USER | PD_PRESENT;
		ptflags = PT_WRITE | PT_USER | PT_PRESENT;
		forwrite = 1;
	}
	else {
		pdflags = PD_USER | PD_PRESENT;
		ptflags = PT_USER | PT_PRESENT;
		forwrite = 0;

	}
	the_mmobj = the_vmarea->vma_obj;
	pagenum = vfn - the_vmarea->vma_start + the_vmarea->vma_off;
	if (forwrite == 1) {
		/* copy on write */
		ret = pframe_get(the_mmobj, pagenum, &the_pframe);
	}
	else {
		ret = pframe_lookup(the_mmobj, pagenum, forwrite, &the_pframe);
	}
	if (ret < 0) {
		proc_kill(curproc, EFAULT);
	}

	dbgq(DBG_THR, "handle_pagefault(): begin to map the address\n");
	paddr = pt_virt_to_phys((uintptr_t)the_pframe->pf_addr);
	ret = pt_map(curproc->p_pagedir, (uintptr_t)PN_TO_ADDR(vfn), paddr, pdflags, ptflags);
	if (ret < 0) {
		proc_kill(curproc, EFAULT);	
	}
	if (forwrite == 1) {
		pframe_dirty(the_pframe);
	}
}
